package VideoTools;

import java.awt.Point;
import java.util.ArrayList;
import java.util.LinkedList;

import VideoTools.AnalyseListOfCircleException.OutOfBoundDirection;
import VideoTools.CircleOutOfBoundException.CircleException;

import dimension2.Circle;

/**
 * @author user
 * this class is used to determine the direction of a list of point, stored in a linkedList.
 * Different algorythme are implemented.
 * To select one, you have to choose in the enum AnalyseAlgorythme.
 */
public class AnalyseListOfCircle {

	// determination of the algorythme
	public enum AnalyseAlgorythme {ALL,ZONE,IMPULSION}
	public static AnalyseAlgorythme algo=AnalyseAlgorythme.ZONE;
	// list which stored all the circle
	private LinkedList<Circle> list;

	// acceptable average for initialisation average.
	private static final int ACCEPTABLE_AVERAGE = 50;

	// speed by default for analyseZone
	private static final int DEFAULT_SPEED = 10;

	// ---------- ANALYSEALLMOVEMENT 
	// acceptable speed for analyseAllMovement algorythme
	private static final int ACCEPTABLE_SPEED = 5;
	// number of circle stored for analyseAllMovement
	private static final int numberOfCircleStored=5;

	// ---------- ANALYSE IMPULSION
	//	 acceptable speed for analyseImpulsion algorythme
	private static final int ACCEPTABLE_SPEED_IMPULSION = 30;
	private static final double DIFF_FIRST_LAST_IMPULSION = 10;

	// ---------- BORDER DETECTION
	private static final int ACCEPTABLE_SPEED_FOR_BORDER_DETECTION=0;
	// 20 % of the size of the screen
	private static final int DistanceOutOkey= VideoPlayer.size_X/3;

	private static final int ACCEPTABLE_SPEED_COMPARE_TO_NEAREST_WALL = 3;

	// calculation of the average.
	private int nbCircle;
	private int total;

	// algo impulsion
	private int cpt_after_circle_stored=0;


	private Movement movement;

	/**
	 * construct a new Analyse list of circle.
	 * the list of circle are stored in a linkedList.
	 */
	public AnalyseListOfCircle(Movement movement){
		this.movement=movement;
		list = new LinkedList<Circle>();

	}


	/** add a circle to the linkedlist
	 * if this circle is outofbound, determine which zone it is.
	 * if there is no circle do nothing
	 * else add the circle at the beginning of the linkedList
	 * @param c
	 * @param exception
	 * @throws AnalyseListOfCircleException
	 */
	public void addCircle(Circle c, CircleException exception) throws AnalyseListOfCircleException{
		switch (exception){
		case CircleOutOfBound:determinationOfZone();movement.set(0, Direction.ANY);break;
		case NoCircle:movement.set(0, Direction.ANY);break;
		case NoException:
			nbCircle++;
			total+=c.getRayon();
			list.addFirst(c);

			if (numberOfCircleStored==list.size()){
				list.removeLast();
			}
			break;
		}
	}


	/** determine the border in which the circle is.
	 *  - calcul the nearest wall 
	 *  - determination of the movement of the list of circle
	 *  - if the last point is in the zone near border ( 20 % ) do :
	 *  	- if the movement speed is superior to a constant, the direction of the exception is set by the movement direction.
	 *  	- else the direction is fixed by the nearest wall;
	 *  - else throw an exception with no direction... (bug)
	 * @throws AnalyseListOfCircleException
	 */
	private void determinationOfZone() throws AnalyseListOfCircleException {

		// determination of the nearest wall, his direction and the distance
		Circle last = list.getFirst();

		int distanceBorderLeftPoint=(int)last.getCenter().getX();
		int distanceBorderRightPoint=VideoPlayer.size_X-(int)last.getCenter().getX();
		int distanceBorderUpPoint=(int)last.getCenter().getY();
		int distanceBorderDownPoint=VideoPlayer.size_Y-(int)last.getCenter().getY();

		int distanceBetweenThePointAndTheNearestWall=distanceBorderLeftPoint;
		Direction wallNearest=Direction.LEFT;

		// right
		if (distanceBorderRightPoint<distanceBetweenThePointAndTheNearestWall){
			distanceBetweenThePointAndTheNearestWall=distanceBorderRightPoint;
			wallNearest=Direction.RIGHT;
		} 
		// up
		if (distanceBorderUpPoint<distanceBetweenThePointAndTheNearestWall){
			distanceBetweenThePointAndTheNearestWall=distanceBorderUpPoint;
			wallNearest=Direction.UP;
		} 
		// down
		if (distanceBorderDownPoint<distanceBetweenThePointAndTheNearestWall){
			distanceBetweenThePointAndTheNearestWall=distanceBorderDownPoint;
			wallNearest=Direction.DOWN;
		}


		if (distanceBetweenThePointAndTheNearestWall < DistanceOutOkey){
			this.analyseAllDeplacement(ACCEPTABLE_SPEED_FOR_BORDER_DETECTION);

			if (movement.getSpeed()>ACCEPTABLE_SPEED_COMPARE_TO_NEAREST_WALL){
				switch (movement.getDirection()){
				case LEFT:throw new AnalyseListOfCircleException(OutOfBoundDirection.LEFT);
				case RIGHT:throw new AnalyseListOfCircleException(OutOfBoundDirection.RIGHT);
				case UP:throw new AnalyseListOfCircleException(OutOfBoundDirection.UP);
				case DOWN:throw new AnalyseListOfCircleException(OutOfBoundDirection.DOWN);
				}
			}
			else {
				switch (wallNearest){
				case LEFT:throw new AnalyseListOfCircleException(OutOfBoundDirection.LEFT);
				case RIGHT:throw new AnalyseListOfCircleException(OutOfBoundDirection.RIGHT);
				case UP:throw new AnalyseListOfCircleException(OutOfBoundDirection.UP);
				case DOWN:throw new AnalyseListOfCircleException(OutOfBoundDirection.DOWN);
				}
			}

		}
		else {
			throw new AnalyseListOfCircleException(OutOfBoundDirection.NO);
		}
	}


	/** determine the average of the circle in the initialisation period.
	 * @return true is the average is less than a constant.
	 */
	public boolean averageCalculation(){
		return total/nbCircle < ACCEPTABLE_AVERAGE;
	}


	/** Use the algorythme define by the field algo
	 * @return
	 */
	public void analyse(){
		switch (algo){
		case ALL:analyseAllDeplacement(ACCEPTABLE_SPEED);break;
		case ZONE:analyseZone();break;
		case IMPULSION:analyseImpulsion();break;
		default: analyseZone(); 
		}
	}



	/** TODO : use derivation methode. 
	 * @return
	 */
	private void analyseImpulsion() {
		if (list.size()==numberOfCircleStored-1){
			if (cpt_after_circle_stored==0){
				System.out.println("analyse");
				analyseAllDeplacement(5);
				if (movement.getSpeed()>0){
					System.out.println("init list");
					cpt_after_circle_stored=numberOfCircleStored;
					list = new LinkedList<Circle>();
				}
			}
			else cpt_after_circle_stored--;
		}
		movement.set(0, Direction.ANY);
	}


	/** analyse all the deplacement 
	 * determine the average of the difference (2by2) of the point in the linkedList list.
	 * -> direction and speed
	 * -> direction : average positive or negative
	 * -> speed = average.
	 * @return a movement with speed and direction.
	 * if the speed is less than the parameter acceptableSpeed, return a movement with 0 speed and ANY direction.
	 */
	private void analyseAllDeplacement(int acceptableSpeed) {

		LinkedList<Circle> copyOfList = (LinkedList<Circle>) list.clone();
		if (copyOfList.size()>2){
			int moyenneX=0;
			int moyenneY=0;
			int compteur=0;
			while(copyOfList.size()>2){
				Circle premier=copyOfList.removeFirst();
				Circle second=copyOfList.removeFirst();
				moyenneX+=premier.getCenter().getX()-second.getCenter().getX();
				moyenneY+=premier.getCenter().getY()-second.getCenter().getY();
				compteur++;
			}
			moyenneX/=compteur;
			moyenneY/=compteur;


			int average=0;
			Direction direction=Direction.ANY;
			//	horizontal
			if (Math.abs(moyenneX) > Math.abs(moyenneY)){
				if (moyenneX > 0){//right
					average = Math.abs(moyenneX);
					direction =Direction.RIGHT;
				}
				else{//left
					average = Math.abs(moyenneX);
					direction =Direction.LEFT;
				}
			}
			else {//vertical
				if (moyenneY > 0){//down
					average = Math.abs(moyenneY);
					direction =Direction.DOWN;
				}
				else{//up
					average = Math.abs(moyenneY);
					direction =Direction.UP;
				}
			}
			if (average > acceptableSpeed)movement.set(average,direction);
		}

	}


	/** analyse the circle.
	 *  split the screen into 8 zones
	 *  if the circle is in the zone...
	 * @return a movement for each zone.
	 */
	private void analyseZone(){
		int x =(int) list.getFirst().getCenter().getX();
		int y =(int) list.getFirst().getCenter().getY(); 
		int oneThirdY = VideoPlayer.size_Y/3;
		int oneThirdX = VideoPlayer.size_X/3;

		// down
		if (y > 2*oneThirdY){
			if (x > 2*oneThirdX) {movement.set(DEFAULT_SPEED,Direction.DOWNRIGHT);}
			else if (x < oneThirdX) {movement.set(DEFAULT_SPEED,Direction.DOWNLEFT);}
			else {movement.set(DEFAULT_SPEED,Direction.DOWN);}
		}
		// up
		else if (y < oneThirdY){
			if (x > 2*oneThirdX){movement.set(DEFAULT_SPEED,Direction.UPRIGHT);}
			else if (x < oneThirdX) {movement.set(DEFAULT_SPEED,Direction.UPLEFT);}
			else {movement.set(DEFAULT_SPEED,Direction.UP);	}
		}
		// center
		else {
			if (x > 2*oneThirdX){movement.set(DEFAULT_SPEED,Direction.RIGHT);}
			else if (x < oneThirdX){movement.set(DEFAULT_SPEED,Direction.LEFT);}
			else {movement.set(DEFAULT_SPEED,Direction.ANY);} 
		}
	}
}
